/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmium;

import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import fr.x9c.cadmium.kernel.AbstractParameters;
import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.ByteCodeParameters;
import fr.x9c.cadmium.kernel.Channel;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Context;
import fr.x9c.cadmium.kernel.Custom;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Interpreter;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.util.RandomAccessInputStream;

/**
 * This class provides primitives related to Cadmium runtime.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.1
 * @since 1.0
 */
@PrimitiveProvider
    public final class Runtime {

        /**
         * No instance of this class.
         */
        private Runtime() {
        } // end empty constructor

        /**
         * Tests whether the current code runner is native.
         * @param ctxt context
         * @param unit ignored
         * @return {@link fr.x9c.cadmium.kernel.Value#TRUE} if the current code
         *         runner is native, {@link fr.x9c.cadmium.kernel.Value#FALSE}
         *         otherwise
         */
        @Primitive
            public static Value cadmium_is_native(final CodeRunner ctxt,
                                                  final Value unit) {
            return ctxt.isNative() ? Value.TRUE : Value.FALSE;
        } // end method 'cadmium_is_native(CodeRunner, Value)'

        /**
         * Returns the current code runner.
         * @param ctxt context
         * @param unit ignored
         * @return the current code runner
         */
        @Primitive
            public static Value cadmium_get_coderunner(final CodeRunner ctxt,
                                                       final Value unit) {
            return Cadmium.createObject(ctxt);
        } // end method 'cadmium_get_coderunner(CodeRunner, Value)'

        /**
         * Returns the current context (state of program, shared by code runners).
         * @param ctxt context
         * @param unit ignored
         * @return the current context
         */
        @Primitive
            public static Value cadmium_get_context(final CodeRunner ctxt,
                                                    final Value unit) {
            return Cadmium.createObject(ctxt.getContext());
        } // end method 'cadmium_get_context(CodeRunner, Value)'

        /**
         * Returns the current parameters.
         * @param ctxt context
         * @param unit ignored
         * @return the current parameters
         */
        @Primitive
            public static Value cadmium_get_parameters(final CodeRunner ctxt,
                                                       final Value unit) {
            return createParameters(ctxt.getContext().getParameters());
        } // end method 'cadmium_get_parameters(CodeRunner, Value)'

        /**
         * Creates an interpreter.
         * @param ctxt context
         * @param params interpreter parameters
         * @param dir initial working directory
         * @param file bytecode file
         * @param in standard input
         * @param out standard output
         * @param err standard error
         * @throws Fail.Exception if interpreter creation raises an exception
         */
        @Primitive
            public static Value cadmium_make_interpreter(final CodeRunner ctxt,
                                                         final Value params,
                                                         final Value dir,
                                                         final Value file,
                                                         final Value in,
                                                         final Value out,
                                                         final Value err)
            throws Fail.Exception {
            try {
                final Context context = ctxt.getContext();
                final Block p = params.asBlock();
                final InputStream stdIn = ((Channel) in.asBlock().asCustom()).asInputStream();
                final PrintStream stdOut = new PrintStream(((Channel) out.asBlock().asCustom()).asOutputStream());
                final PrintStream stdErr = new PrintStream(((Channel) err.asBlock().asCustom()).asOutputStream());
                final ByteCodeParameters ip =
                    new ByteCodeParameters(createStringArray(p.get(1)),
                                           p.get(4) == Value.TRUE,
                                           p.get(5) == Value.TRUE,
                                           stdIn,
                                           stdOut,
                                           stdErr,
                                           p.get(8) == Value.TRUE,
                                           p.get(9) == Value.TRUE,
                                           p.get(10) == Value.TRUE,
                                           p.get(11) == Value.TRUE,
                                           p.get(12).asBlock().asString(),
                                           p.get(13) == Value.TRUE,
                                           p.get(0).asBlock().asString(),
                                           false,
                                           null,
                                           false,
                                           false,
                                           false,
                                           false,
                                           p.get(2).asLong(),
                                           p.get(3).asLong(),
                                           createStringArray(p.get(6)),
                                           p.get(7) == Value.TRUE);
                return Cadmium.createObject(new Interpreter(ip,
                                                            context.getRealFile(dir),
                                                            new RandomAccessInputStream(context.getInputStreamForPath(file))));
            } catch (final Exception e) {
                Cadmium.fail(ctxt, e);
                return Value.UNIT; // never reached
            } // end try/catch
        } // end method 'cadmium_make_interpreter(CodeRunner, Value, Value, ...)'
        /**
         * Exact synonym of {@link #cadmium_make_interpreter(CodeRunner, Value, Value, Value, Value, Value, Value)}.
         * @param ctxt context
         * @param params interpreter parameters
         * @param dir initial working directory
         * @param file bytecode file
         * @param in standard input
         * @param out standard output
         * @param err standard error
         * @throws Fail.Exception if interpreter execution raises an exception
         */
        @Primitive
            public static Value cadmium_make_interpreter_native(final CodeRunner ctxt,
                                                                final Value params,
                                                                final Value dir,
                                                                final Value file,
                                                                final Value in,
                                                                final Value out,
                                                                final Value err)
            throws Fail.Exception {
            return cadmium_make_interpreter(ctxt,
                                            params,
                                            dir,
                                            file,
                                            in,
                                            out,
                                            err);
        } // end method 'cadmium_make_interpreter_native(CodeRunner, Value, ...)'

        /**
         * Executes an interpreter.
         * @param ctxt context
         * @param interp interpreter to execute
         * @return result of interpretation
         * @throws Fail.Exception if interpreter execution raises an exception
         */
        @Primitive
            public static Value cadmium_interpreter_execute(final CodeRunner ctxt,
                                                            final Value interp)
            throws Fail.Exception {
            final Interpreter intrp = (Interpreter) interp.asBlock().asCustom();
            final Value res;
            try {
                res = intrp.execute();
            } catch (final Exception e) {
                Cadmium.fail(ctxt, e);
                return Value.UNIT; // never reached
            } // end try/catch

            return copyValue(res, intrp.getContext(), ctxt.getContext());
        } // end method 'cadmium_interpreter_execute(CodeRunner, Value)'

        /**
         * Executes a callback in an interpreter.
         * @param ctxt context
         * @param interp interpreter to execute callback in
         * @param n callback name
         * @param p callback parameters
         * @return result of interpretation
         * @throws Fail.Exception if interpreter execution raises an exception
         */
        @Primitive
            public static Value cadmium_interpreter_execute_callback(final CodeRunner ctxt,
                                                                     final Value interp,
                                                                     final Value n,
                                                                     final Value p)
            throws Fail.Exception {
            final Interpreter intrp = (Interpreter) interp.asBlock().asCustom();
            final List<Value> params = new ArrayList<Value>();
            Value list = p;
            while (list.isBlock()) {
                final Block b = list.asBlock();
                params.add(b.get(0));
                list = b.get(1);
            } // end while
            final Value res;
            try {
                res = intrp.execute(n.asBlock().asString(),
                                    params.toArray(new Value[params.size()]));
            } catch (final Exception e) {
                Cadmium.fail(ctxt, e);
                return Value.UNIT; // never reached
            } // end try/catch

            return copyValue(res, intrp.getContext(), ctxt.getContext());
        } // end method 'cadmium_interpreter_execute_callback(CodeRunner, Value)'

        /**
         * Returns the context of an interpreter
         * (state of program, shared by code runners).
         * @param ctxt context
         * @param interp interpreter to get context from
         * @return the context of the passed interpreter
         */
        @Primitive
            public static Value cadmium_interpreter_get_context(final CodeRunner ctxt,
                                                                final Value interp) {
            return Cadmium.createObject(((Interpreter) interp.asBlock().asCustom()).getContext());
        } // end method 'cadmium_interpreter_get_context(CodeRunner, Value)'

        /**
         * Returns the parameters of an interpreter.
         * @param ctxt context
         * @param interp interpreter to get parameters from
         * @return the parameters of the passed interpreter
         */
        @Primitive
            public static Value cadmium_interpreter_get_parameters(final CodeRunner ctxt,
                                                                   final Value interp) {
            return createParameters(((Interpreter) interp.asBlock().asCustom()).getContext().getParameters());
        } // end method 'cadmium_interpreter_get_parameters(CodeRunner, Value)'

        /**
         * Creates a value of type <i>Cadmium.interpreter_parameters</i> from
         * an actual {@link fr.x9c.cadmium.kernel.AbstractParameters} instance.
         * @param ap parameters as a Java instance - should not be <tt>null</tt>
         * @return parameters as a <i>Cadmium.interpreter_parameters</i> value
         */
        private static Value createParameters(final AbstractParameters ap) {
            assert ap != null : "null ap";
            final String file;
            final int initStackSize;
            final int maxStackSize;
            final String[] providers;
            final boolean compiled;
            if (ap instanceof ByteCodeParameters) {
                final ByteCodeParameters bcp = (ByteCodeParameters) ap;
                file = bcp.getFile();
                initStackSize = bcp.getInitStackSize();
                maxStackSize = bcp.getMaxStackSize();
                providers = bcp.getProviders();
                compiled = bcp.isDispatcherCompiled();
            } else {
                file = "native";
                initStackSize = -1;
                maxStackSize = -1;
                providers = new String[0];
                compiled = false;
            } // end if/else
            final Block res = Block.createBlock(13, 0);
            res.set(0, Value.createFromBlock(Block.createString(file)));
            res.set(1, Value.createFromBlock(Block.createStringArray(ap.getArguments())));
            res.set(2, Value.createFromLong(initStackSize));
            res.set(3, Value.createFromLong(maxStackSize));
            res.set(4, ap.isBacktraceRequested() ? Value.TRUE : Value.FALSE);
            res.set(5, ap.isExitStoppingJVM() ? Value.TRUE : Value.FALSE);
            res.set(6, Value.createFromBlock(Block.createStringArray(providers)));
            res.set(7, compiled ? Value.TRUE : Value.FALSE);
            res.set(8, ap.isFrameAWT() ? Value.TRUE : Value.FALSE);
            res.set(9, ap.isBareCanvas() ? Value.TRUE : Value.FALSE);
            res.set(10, ap.isJavaxSoundUsed() ? Value.TRUE : Value.FALSE);
            res.set(11, Value.createFromBlock(Block.createString(ap.getOS())));
            res.set(12, ap.isUnixEmulated() ? Value.TRUE : Value.FALSE);
            return Value.createFromBlock(res);
        } // end method 'createParameters(AbstractParameters)'

        /**
         * Creates a Java string array from an OCaml one.
         * @param v OCaml string array - should not be <tt>null</tt>
         * @return string array equivalent to the passed one
         */
        private static String[] createStringArray(final Value v) {
            assert v != null : "null v";
            final Block b = v.asBlock();
            final int len = b.getWoSize();
            final String[] res = new String[len];
            for (int i = 0; i < len; i++) {
                res[i] = b.get(i).asBlock().asString();
            } // end for
            return res;
        } // end method 'createStringArray(Value)'

        /**
         * Copy a value from a context to another one.
         * Similar to {@link fr.x9c.cadmium.support.Helper#duplicate(fr.x9c.cadmium.kernel.Value, fr.x9c.cadmium.kernel.Interpreter, fr.x9c.cadmium.kernel.Interpreter)}.
         * @param v value to copy
         * @param from source context (should be the context of <i>v</i>)
         * @param to destination context
         * @return a value equivalent to <i>v</i> that can be used in <i>to</i>
         */
        private static Value copyValue(final Value v,
                                       final Context from,
                                       final Context to) {
            if (v.isBlock()) {
                if (from.isAtom(v)) {
                    return to.getAtom(v.asBlock().getTag());
                } else {
                    return Value.createFromBlock(v.asBlock().duplicate());
                } // end if/else
            } else {
                return v;
            } // end if/else
        } // end method 'copyValue(Value, Context, Context)'

    } // end class 'Runtime'
